En el presente estudio se procederá a analizar el data set llamado “Netflix Prize data” obtenido en la página web Kaggle. Dicho dataset se creó como una competición para encontrar el mejor algoritmo de predicción de la puntuación que los usuarios dan a las películas de la plataforma.
Antes de extraer la información y comenzar a trabajar sobre ella se ha procedido a visualizar a qué tipo de datos íbamos a enfrentarnos. El dataset contiene diversos archivos pero solo nos centraremos con los que vamos a trabajar en este estudio:
Los archivos ‘combined_data_{nº}’ contienen la información de las puntuaciones de los usuarios para determinado código de película, así como el identificador de usuario y la fecha (día, mes y año) en la que se dio dicha valoración.
Esta base de datos cuenta con un tamaño de espacio en memoria demasiado elevado, por lo que se procederá a extraer los datos de 250 películas escogidas de manera aleatoria con una semilla (“2495”).
Otro de los archivos que se utilizarán en este estudio contiene el título de cada ID asociado a las películas además del año en el que se estrenó. En secciones posteriores explicaremos como realizaremos la inclusión de estos datos junto con los datos iniciales para obtener el dataset final con el que trabajaremos de forma más cómoda.
Una vez realizado este proceso generaremos una nueva variable para incluirla en nuestro estudio ya que sería interesante medir la diferencia de años transcurridos desde el estreno hasta la puntuación de las diferentes películas (“YearsSinceRelease”).
El primer paso para realizar la carga de datos es leer el fichero “filas_ID_combined_all.txt” de donde obtendremos por cada ID de película el número del fichero donde se encuentran las puntuaciones de cada usuario y las posiciones de la fila inicial y final de estas. De la misma forma, cargaremos los cuatro ficheros ‘combined_data_{nº}’ con la información de las puntuaciones de las películas.
Ya que el fichero “filas_ID_combined_all.txt” cuenta con la información de todas las películas, después de exportarlo realizaremos un filtrado para quedarnos solo con las filas de las 250 películas que nos interesan estudiar. Seguidamente, utilizaremos la función obtain_movies para que, pasados los parámetros del fichero “filas_ID_combined_all.txt” y el ID de ‘combined_data_{nº}’, calculemos todas las posiciones a leer y nos devuelva un combined_data con solo las observaciones de las películas que sabemos que se encuentran en dicho fichero.
#Función que obtiene los datos de filas específicas
obtain_movies = function(index_data, combined_data, idx){
#Obtenemos las películas del archivo a leer (1,2,3 o 4)
rows_data = filter(index_data, data == idx)
rows_data = select(rows_data, fila, fila_final)
#Por cada fila guardamos el rango de posiciones a leer en el fichero
ranges = c()
for(i in 1:nrow(rows_data)){
ranges = c(ranges, rows_data[[i,1]]:rows_data[[i,2]])
}
#Nos quedamos solo con los datos de las películas de la muestra
combined_data = slice(combined_data, ranges)
return(combined_data)
}#Leemos el índice
filas_ID_combined_all = read_csv("data/filas_ID_combined_all.txt")
#Cargamos los ficheros con la información de Netflix
combined_data1 = read_tsv("data/combined_data_1.txt", col_names=FALSE)
combined_data2 = read_tsv("data/combined_data_2.txt", col_names=FALSE)
combined_data3 = read_tsv("data/combined_data_3.txt", col_names=FALSE)
combined_data4 = read_tsv("data/combined_data_4.txt", col_names=FALSE)
#Obtenemos nuestra muestra de películas
n_muestra = 250
set.seed(2495)
muestra_grupo = sample(1:17770, n_muestra, replace = FALSE)
#Nos quedamos con el índice de las películas de nuestra muestra
filas_ID_combined_all = filter(filas_ID_combined_all, filas_ID_combined_all$ID %in% muestra_grupo)
filas_ID_combined_all = select(filas_ID_combined_all, ID, fila, fila_final, data)
#Mostramos cuántas películas debemos leer de cada fichero
#table(filas_ID_combined_all$data)
#Leemos las observaciones de las películas de nuestra muestra
data1 = obtain_movies(filas_ID_combined_all, combined_data1, 1)
data2 = obtain_movies(filas_ID_combined_all, combined_data2, 2)
data3 = obtain_movies(filas_ID_combined_all, combined_data3, 3)
data4 = obtain_movies(filas_ID_combined_all, combined_data4, 4)
#Juntamos los datos de las películas de los 4 ficheros
df_ratings = rbind(data1,data2,data3,data4)
#Exportamos los datos de las películas de nuestra muestra
write.table(df_ratings,"data/combined_data_sample.txt", row.names = FALSE, col.names = FALSE)
#Eliminamos variables que ya no son útiles
rm(combined_data1, combined_data2, combined_data3, combined_data4, data1, data2, data3, data4, filas_ID_combined_all)Una vez hemos exportado los ficheros necesarios y filtrado las observaciones de interés, procedemos a la limpieza de datos y construcción del modelo de datos final. Generamos un dataframe que por cada observación contenga: ID de la película, ID de usuario, calificación y fecha de calificación.
#Se asigna una posición a cada observación para posteriormente indicar el id de película de cada una de ellas
df_ratings = df_ratings %>%
mutate(Idx = row_number())
#Guardamos la fila donde empieza cada película
movie_rows = grep(":", df_ratings$X1)
#Añadimos el id de la película a cada posición y eliminamos el caracter ":"
rows_ID = df_ratings %>%
filter(Idx %in% movie_rows) %>%
mutate(X1 = as.integer(gsub(":","",X1)))
#Número de veces que se tendrá que repetir el identificador de cada película
reps = diff(c(rows_ID$Idx, max(df_ratings$Idx) + 1))
netflix = df_ratings %>%
mutate(MovieID = rep(rows_ID$X1, times = reps)) %>%
filter(!(Idx %in% rows_ID))
#Se definen las columnas del dataframe
netflix = netflix %>%
separate(X1,into = c("UserID","Rating","RatingDate"), sep = ",") %>%
na.omit(netflix) %>%
mutate(Idx = row_number())
#Se eliminan las variables auxiliares
rm(df_ratings, movie_rows, rows_ID, reps)Con el objectivo de completar nuestro dataset, realizamos la unión de la tibble netflix con los datos exportados del fichero movies_titles.csv, añadiendo así la variable del nombre de la película y el año de estreno. Una vez se ha realizado el inner join, se procede al cambio de tipo de cada variable para que concuerde con la información que representan.
#Lectura del fichero movie_titles.csv
df_movies = read_tsv("data/movie_titles.csv", locale = readr::locale(encoding = "ISO-8859-1"), col_names=FALSE)
df_movies = df_movies %>%
separate(X1,",",into =c("MovieID","MovieRelease","Title"), extra="merge")
df_movies = filter(df_movies, df_movies$MovieID %in% muestra_grupo)
#Se transforma la variable MovieRelease a numérica
netflix$MovieID = as.character(netflix$MovieID)
#Se unifica el dataframe de las puntuaciones con el de las películas
netflix = inner_join(x = netflix, y = df_movies, by = "MovieID", all = TRUE)
#Se transforma la variable 'Rating' a tipo númerica
netflix$Rating = as.numeric(netflix$Rating)
#Se transforma la variable 'RatingDate' a tipo date
netflix$RatingDate = as.Date(netflix$RatingDate, format = "%Y-%m-%d")
#Se transforma la variable MovieRelease a numérica
netflix$MovieRelease = as.numeric(netflix$MovieRelease)
#Se añade la diferencia en años entre el año de puntuación y el de estreno de la película
netflix = mutate(netflix, YearsSinceRelease = year(RatingDate) - MovieRelease)
#Se transforma la variable YearsSinceRelease a numérica
netflix$YearsSinceRelease = as.numeric(netflix$YearsSinceRelease)
#Adicionalmente, dividimos la variable RatingDate en: día, mes y año
sep = netflix %>%
separate(RatingDate,into = c("Year","Month","Day"), sep = "-") %>%
na.omit(netflix) %>%
mutate(Idx = row_number())
netflix = mutate(netflix, sep)
rm(sep)
#Se ordenan las posiciones de las columnas y se indican su nuevo nombre
netflix = select(netflix, Idx, MovieID, Title, UserID, Rating, Day, Month, Year, RatingDate, MovieRelease, YearsSinceRelease)
#Exportamos el fichero preparado y construido para empezar a trabajar en su análisis
write.csv(netflix, "data/netflix.csv", row.names = FALSE)Una vez tenemos la tibble final sobre la que vamos a trabajar, procedemos a estudiar su estructura:
## tibble [1,093,856 x 11] (S3: tbl_df/tbl/data.frame)
## $ Idx : int [1:1093856] 1 2 3 4 5 6 7 8 9 10 ...
## $ MovieID : chr [1:1093856] "43" "43" "43" "43" ...
## $ Title : chr [1:1093856] "Silent Service" "Silent Service" "Silent Service" "Silent Service" ...
## $ UserID : chr [1:1093856] "305151" "497196" "2327803" "2625420" ...
## $ Rating : num [1:1093856] 4 3 1 2 5 2 1 3 5 2 ...
## $ Day : chr [1:1093856] "20" "13" "19" "13" ...
## $ Month : chr [1:1093856] "02" "04" "08" "05" ...
## $ Year : chr [1:1093856] "2005" "2003" "2001" "2004" ...
## $ RatingDate : Date[1:1093856], format: "2005-02-20" "2003-04-13" ...
## $ MovieRelease : num [1:1093856] 2000 2000 2000 2000 2000 2000 2000 2000 2000 2000 ...
## $ YearsSinceRelease: num [1:1093856] 5 3 1 4 3 3 5 2 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:250] 1 107 293 1390 3766 4458 5563 5738 8145 8489 ...
## ..- attr(*, "names")= chr [1:250] "1" "107" "293" "1390" ...
Idx: Variable numérica que determina la posición o índice de cada observación del dataset.
MovieID: Variable [TODO] que identifica a cada película con un ID determinado, este ID está asociado a un título que será representado por la variable “Title”.
Title: Variable con datos definidos como caracteres. Esta variable representa el título de cada una de las películas identificadas con un ID en la variable “MovieID”.
UserID: Variable con datos definidos como caracteres que representa el ID único del usuario que ha calificado la película.
Rating: Variable numérica ordinal que representa la calificación que ha hecho cada usuario de las películas que ha puntuado. Esta calificación puede ser dentro del rango [1,2,3,4,5], donde 1 estrella representa la calificación más baja y 5 estrellas la calificación más alta.
RatingDate: Variable definida como tipo **date*, que representa la fecha en la cual el usuario realizó la calificación de una determinada película.
MovieRelease: Variable numérica que indica el año en el cual se estrenó la película. El año de estreno puede referirse tanto a estreno de una determinada película en el cine como en DVD.
YearsSinceRelease: Variable numérica la cual se ha calculado mediante la resta de RatingDate y MovieRelease. Esta variable indica el número de años que ha pasado desde que se ha estrenado una determinada película hasta que el usuario la ha calificado.
Analizar número de votaciones de las diferentes películas. ¿Cuántas películas hay de cada rating (1,2,3,4 y 5 estrellas)?
ggplot(data = netflix, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "lightcoral", high = "firebrick2")+
labs(x = "Rating", y = "Number of ratings", title = "Total Ratings")+
scale_y_continuous(limits=c(0,380000), labels = scales::comma)+
coord_flip() +
theme_classic()
# Image in the visualization
image = image_read("imgs/icon-rating.png")
grid.raster(image, x = 0.80, y = 0.25, height = 0.23)Como se puede observar en el gráfico, las puntuaciones generalmente han sido positivas ya que la mayoria de los casos tienen una puntuación superior a 3.
La calificación de 4 que ha sido dada por un total de 2926 usuarios, es la que se ha dado más veces.
La calificación media de todas las películas es de 3.37.
df_movies = filter(df_movies, df_movies$MovieID %in% muestra_grupo)
calif_año = table(df_movies$MovieRelease)
df_movies$MovieRelease = as.numeric(df_movies$MovieRelease)
break_range = seq(from = 1920, to = 2010, by = 10)
hist_plot1 = ggplot(data = df_movies) +
geom_histogram(aes(x = MovieRelease, y = ..count../sum(..count..)),
breaks = break_range,
color = c("darkgoldenrod2", "lightgoldenrod1", "lightblue", "palegreen3",
"lightpink2", "plum3", "steelblue3", "darkolivegreen3", "salmon3"),
fill = c("darkgoldenrod1", "khaki1", "lightblue", "palegreen", "lightpink1",
"plum2", "steelblue1", "darkolivegreen2", "salmon1"),
size = 1) +
geom_density(aes(x = MovieRelease, y = 10*..density..),
color = "black",
fill = "seashell3",
size = 1,
alpha = 0.2,
show.legend = FALSE) +
scale_x_continuous(breaks = break_range) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.5)) +
scale_color_gradient(low = "indianred1", high = "red2") +
ylab("% of movies") +
xlab("Movie release's decade") +
ggtitle("Percentage of movies released by decade") +
theme_minimal()
ggplotly(hist_plot1)bar_plot2 = ggplot(data = netflix) +
geom_bar(aes(x = Month, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
show.legend = FALSE,
color = c("darkslategray3", "darkslategray3", "darkslategray3", "lightpink2",
"lightpink2", "lightpink2", "lightgoldenrod2", "lightgoldenrod2", "lightgoldenrod2",
"tan2", "tan2", "tan2"),
fill = c("darkslategray2", "darkslategray2", "darkslategray2", "lightpink1",
"lightpink1", "lightpink1", "lightgoldenrod1", "lightgoldenrod1", "lightgoldenrod1",
"tan1", "tan1", "tan1"),
size = 1) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.15)) +
scale_x_discrete(labels = month.abb) +
ylab("% Ratings") +
xlab("Rating month") +
ggtitle("Percentage of ratings by month") +
theme_minimal()
ggplotly(bar_plot2)netflix$DayOfWeek = weekdays(netflix$RatingDate)
bar_plot3 = ggplot(data = netflix) +
geom_bar(aes(x = DayOfWeek, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
show.legend = FALSE,
color = c("indianred4", "lightgoldenrod2", "palevioletred2", "palegreen4",
"tan2", "steelblue3", "mediumorchid4"),
fill = c("indianred3", "lightgoldenrod1", "palevioletred1", "palegreen3",
"tan1", "steelblue2", "mediumorchid3"),
size = 1) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.25)) +
ylab("% Ratings") +
xlab("Rating day of week") +
ggtitle("Percentage of ratings by day of week") +
theme_minimal()
ggplotly(bar_plot3)movies_table_title = sort(table(netflix$Title), decreasing = TRUE)
movies_table_title = as.data.frame(movies_table_title)
set.seed(2)
ggplot(movies_table_title, aes(label = Var1, size = Freq, color = Freq, angle = sample(c(0,15,30, 45,60, 75,90, 105,120,135, 160), 250, replace = TRUE))) +
geom_text_wordcloud_area(mask = png::readPNG("imgs/netflix-mask2.png"),
rm_outside = TRUE, res = 100) +
scale_color_gradient(low = "indianred1", high = "red2") +
theme_minimal()wordcloud2(movies_table_title, size = 0.3, color = 'red')import pandas as pd
netflix_py = pd.read_csv("data/netflix.csv")
res = pd.concat([netflix_py.groupby('MovieID')['Rating'].describe(),
netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.mode).rename('mode'),
netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.median).rename('median')
], axis=1).T
print(res)## MovieID 43 195 ... 17606 17751
## count 105.000000 185.000000 ... 87.000000 2475.000000
## mean 2.571429 3.297297 ... 2.229885 3.938182
## std 1.284951 1.039044 ... 1.075100 1.133302
## min 1.000000 1.000000 ... 1.000000 1.000000
## 25% 1.000000 3.000000 ... 1.000000 3.000000
## 50% 3.000000 3.000000 ... 2.000000 4.000000
## 75% 3.000000 4.000000 ... 3.000000 5.000000
## max 5.000000 5.000000 ... 5.000000 5.000000
## mode 3.000000 3.000000 ... 1.000000 5.000000
## median 3.000000 3.000000 ... 2.000000 4.000000
##
## [10 rows x 250 columns]
#Ordenamos las MovieID en función del número de calificaciones y de forma descendente:
movies_table_name = table(netflix$Title)
orden_name = sort(movies_table_name, decreasing = TRUE)
#Tabla agrupada por película y año del número de valoraciones.
tabla = table(netflix$Title, netflix$Year)
tabla = as.data.frame(tabla)
tabla = filter(tabla, Var1 %in% names(orden_name[1:10])) %>%
rename(MovieTitle = Var1, Year = Var2)
#Representación gráfica.
line_plot = ggplot(tabla, aes(x = Year, y = Freq,
group = MovieTitle,
color = MovieTitle,
shape = MovieTitle)) +
scale_shape_manual(values=1:10) +
geom_point() +
geom_line() +
ylab("Ratings number") +
xlab("Rating's year") +
theme_minimal()
ggplotly(line_plot)## Warning: `group_by_()` is deprecated as of dplyr 0.7.0.
## Please use `group_by()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.
#Compara sus estadísticos y distribuciones(histogramas, boxplot, violin plot,. . . )
#Distribución del score promedio por año.
movies_table = table(netflix$MovieID)
orden = sort(movies_table, decreasing = TRUE)
a = filter (netflix, MovieID %in% names(orden[1:10]))
res = a %>%
group_by(MovieID, Year) %>%
summarise(mean = mean(Rating))#Una vez sabemos el MovieID de las películas más votadas las filtramos:
mas_votadas = filter(netflix, MovieID == names(orden[1:5]))
mas_votadas = as.data.frame(mas_votadas)
#Representamos
positions = c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")
bar_plot3 = ggplot(data = netflix) +
geom_bar(aes(x = DayOfWeek, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
show.legend = FALSE,
color = c("indianred4", "lightgoldenrod2", "palevioletred2", "palegreen4",
"tan2", "steelblue3", "mediumorchid4"),
fill = c("indianred3", "lightgoldenrod1", "palevioletred1", "palegreen3",
"tan1", "steelblue2", "mediumorchid3"),
size = 1) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.25)) +
scale_x_discrete(limits = positions)+
ylab("% Ratings") +
xlab("Rating day of week") +
ggtitle("Percentage of ratings by day of week") +
theme_minimal()
div(ggplotly(bar_plot3), align = "center")#Más votada
df_more_voted = netflix[netflix$MovieID == names(which.max(movies_table)),]
ggplot(data = df_more_voted, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "cadetblue1", high = "cadetblue4")+
labs(x = "Rating", y = "Number of ratings", title = df_more_voted$Title[1])+
scale_y_continuous(limits=c(0,45000))+
coord_flip() +
theme_classic()
#Image in the visualization
image = image_read("imgs/masvotada.jpg")
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)#Menos votada
df_less_voted = netflix[netflix$MovieID == names(which.min(movies_table)),]
ggplot(data = df_less_voted, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "darkgoldenrod1", high = "darkgoldenrod3")+
labs(x = "Rating", y = "Number of ratings", title = df_less_voted$Title[1])+
scale_y_continuous(limits=c(0,8))+
coord_flip() +
theme_classic()
# Image in the visualization
image = image_read("imgs/menosvotada.jpg")
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)